#ifndef __NAMED_METHOD_INVOKE__
#define __NAMED_METHOD_INVOKE__

namespace nmi
{
	struct imethod_t : RefalbeImp
	{
		refp<IRefable>	implo;
		virtual long invoke( xv::xvalue_t & args, xv::xvalue_t & retv ) = 0;
	};

	struct null_t
	{};

	static null_t	nullparam;

	template < class T, class EXPARAM = null_t >
	struct method_t : imethod_t
	{
		typedef long ( T::* name_handler )( xv::xvalue_t & args, xv::xvalue_t & retv, EXPARAM extarg );
		EXPARAM			_exparam;
		name_handler	_handler;
		method_t( T * self, name_handler handler )
		{
			implo = self;
			_handler = handler;
		}
		method_t( T * self, name_handler handler, EXPARAM exp )
			: _exparam(exp)
		{
			implo = self;
			_handler = handler;
		}
		virtual long invoke( xv::xvalue_t & args, xv::xvalue_t & retv )
		{
			T * _this = (T*)(implo.m_p);
			return (_this->*_handler)( args, retv, _exparam );
		}
	};

	template < class T, class F, class EXPARAM >
	static imethod_t* methodilize( T * self, F fnp, EXPARAM exp )
	{
		typedef method_t<T,EXPARAM> __method_t;
		__method_t * mo = new __method_t( self, fnp, exp );
		if( !mo ) return NULL;
		return mo;
	}

	struct xv_dumper : xv::Output 
	{
		cblob_t					_blob;
		std::vector<uint8_t>	_bb;
		virtual bool write( const void * p, size_t cb )
		{
			try {
				_bb.insert( _bb.end(), (uint8_t*)p, (uint8_t*)p + cb );
				return true;
			} catch ( ... ) {
			}
			return false;
		}
		cblob_t & binilze()
		{
			_blob.Data = _bb.data();
			_blob.Length = _bb.size();
			return _blob;
		}
		cblob_t & binilze( xv::xvalue_t & v )
		{
			_bb.clear();
			if( any_save_to( &v, this ) )
			{
				_blob.Data = _bb.data();
				_blob.Length = _bb.size();
			}
			else
			{
				_blob.Data = 0;
				_blob.Length = 0;
			}
			return _blob;
		}
	};

	struct asynctx_t
	{
		HINVOKE		hInvoke;
		OUT_PARAM	outBuff;
	};

	/*
	proticol
	
	send
	{
		tag	: "nmi",
		ver	: 1,
		call: "{$func}",
		args: {
				argn : argv,
				argn : argv,
				argn : argv,
				argn : argv,
				argn : argv,
			}
	}
	recv
		any

	*/

	static bool make_proticol_xv( const char * name, xv::xvalue_t & args, xv::xvalue_t & package )
	{
		try {
			package.clear();
			package.asObject();
			package["tag"] = "nmi";
			package["ver"] = 1;
			package["call"] = name;
			package["args"] = args;
			return true;
		}
		catch( ... )
		{
			
		}
		return false;
	}

	static bool check_proticol_xv( xv::xvalue_t & package )
	{
		xv::xvalue_t _tmp = package["tag"];

		if( stricmp( _tmp, "nmi" ) ) 
			return false;

		_tmp = package["ver"];
		if( (int32_t)_tmp != 1 ) return false;

		_tmp = package["call"];
		if( !_tmp.isMBS() ) return false;

		_tmp = package["args"];
		if( !_tmp.isObject() ) return false;

		return true;
	};

	struct Param
	{
		std::string		_name;
		xv::xvalue_t	_valu;
		Param( const char * name, xv::xvalue_t val )
		{
			_name = name;
			_valu = val;
		}
	};

	struct invocation
	{
		HREMOTE			_remote;
		size_t			_opts;
		xv::xvalue_t	_args;

		struct argsetter
		{
			invocation&		_invoc;
			std::string		_name;

			argsetter( invocation& o, const char * name) 
				: _invoc( o )
				, _name(name)
			{
			
			}
			invocation&	operator = ( xv::xvalue_t tmp )
			{
				_invoc.argset( _name.c_str(), tmp );
				return _invoc;
			}

		};

		enum 
		{
			opt_close_handle = 1,
		};

		invocation( HREMOTE h, bool close_on_destroy = false ) : _remote(h), _opts(0)
		{
			_args.asObject();
			if( close_on_destroy ) 
				_opts |= opt_close_handle;
		}
		invocation( const wchar_t * p ) : _opts(0)
		{
			if( SUCCEEDED( LacOpenRemote( p, &_remote ) ) )
				_opts |= opt_close_handle;
		}
		~invocation()
		{
			close();
		}

		invocation& operator << ( Param & nv )
		{
			argset( nv._name.c_str(), nv._valu );
			return *this;
		}

		invocation& set_args( xv::xvalue_t & x )
		{
			_args = x;
			return *this;
		}

		argsetter operator [] ( const char * name )
		{
			return argsetter(*this, name);
		}
		invocation & rewind()
		{
			_args.clear();
			return *this;
		}

		invocation & argset( const char * name, xv::xvalue_t val )
		{
			_args.set( name, val );
			return *this;
		}

		long invoke( const char * name, xv::xvalue_t & retv, uint32_t timeout = -1 )
		{
			xv::xvalue_t package;
			if( !make_proticol_xv( name, _args, package ) )
				return E_INVALIDARG;
			
			xv_dumper package_dump;
			if( !package.save( package_dump ) ) return E_INVALIDARG;

			blob_t about;
			long remote_lr = 0;
			long lr = LacInvokeSync( _remote, "nmi:do", package_dump.binilze(), &remote_lr, about, timeout );
			if( lr < 0 ) return lr;

			if( remote_lr < 0 ) return remote_lr;
			
			if( !retv.load( about ) )
				return E_FAIL;
			
			return S_OK;
		}

		void close()
		{
			if( _opts & opt_close_handle )
				LacCloseRemote(_remote);
			_remote = 0;
			_args.clear();
			_opts = 0;
			_args.asObject();
		}
	};

	class station_t
	{
	protected:
		
		typedef std::map< std::string, refp<imethod_t> > method_map_t;

		method_map_t	_methods;
		long			_init_lr;
	public:

		station_t()
		{
			_init_lr = LacIsValid() ? S_OK : E_UNEXPECTED;
			if( FAILED(_init_lr) ) return ;
			_init_lr = LacRegisterFunction( "nmi:do", nmi_do, this );
		}
		~station_t()
		{
			if( _init_lr < 0 ) return ;
			LacRevokeFunction( "nmi:do" );
			_methods.clear();
		}

		bool is_valid()
		{
			return SUCCEEDED(_init_lr);
		}

		long set_publish_name( const wchar_t * center_name )
		{
			RFAILED( _init_lr );
			return ( LacSetPublishName( center_name ) );
		}

		long startup( uint32_t minThreads = 2, size_t maxCalls = 100, bool wait = false )
		{
			RFAILED( _init_lr );
			return LacListen( minThreads, maxCalls, wait );
		}

		static long WINAPI nmi_do( HINVOKE hInvoke, LAC_BLOB_C & InParam, OUT_PARAM & outParam, LPVOID vpOpaque )
		{
			try 
			{
				xv::xvalue_t package;

				if( !package.load( InParam ) )
					return E_INVALIDARG;

				if( !check_proticol_xv( package ) )
					return E_INVALIDARG;

				asynctx_t async;
				async.hInvoke = hInvoke;
				async.outBuff = outParam;

				xv::xvalue_t aout;
				long lr = ((station_t*)vpOpaque)->_nmi_do( async, package, aout );
				if( FAILED(lr) ) return lr;
				
				xv_dumper dmp;
				if( !aout.save( dmp ) )
					return E_FAIL;

				return LacSetOutParam( outParam, dmp.binilze() );

			}
			catch( ... )
			{
			
			}

			return E_FAIL;
		}

		long _nmi_do( asynctx_t & async, xv::xvalue_t & package, xv::xvalue_t & aout )
		{
			xv::xvalue_t	name = package["call"];
			xv::xvalue_t	args = package["args"];

			method_map_t::iterator it = _methods.find( name );
			if( it == _methods.end() )
				return E_FAIL;

			refp<imethod_t> method = it->second;

			return method->invoke( args, aout );
		}

		static long async_complete_invoke( asynctx_t & async, xv::AnyValue & aout )
		{
			xv_dumper dmp;
			HRESULT hr = any_save_to( &aout, &dmp ) ? S_OK : E_FAIL;
			HRESULT hr2 = LacCompleteInvoke( async.hInvoke, async.outBuff, hr, dmp.binilze() );
			return MIN(hr,hr2);
		}

		long bind_method( const char * name, imethod_t* pmo, imethod_t ** ppmo = 0 )
		{
			RFAILED( _init_lr );
			if( !name || !pmo ) return E_INVALIDARG;
			try 
			{
				std::string tmp( name );
				method_map_t::iterator it = _methods.find( tmp );
				if( it != _methods.end() ) 
				{
					if( ppmo ) *ppmo = it->second.detach();
					else return E_FAIL;
					it->second = pmo;
				}
				else
				{
					_methods[tmp] = pmo;
				}
				return S_OK;
			}
			catch ( ... )
			{
				
			}
			return E_FAIL;
		}

		long revoke_method( const char * name, imethod_t ** ppmo = 0 )
		{
			RFAILED( _init_lr );
			if( !name ) return E_INVALIDARG;
			try 
			{
				std::string tmp( name );
				method_map_t::iterator it = _methods.find( tmp );
				if( it == _methods.end() ) 
					return S_OK;

				if( ppmo ) 
					*ppmo = it->second.detach();

				_methods.erase( it );
				return S_OK;
			}
			catch ( ... )
			{
				
			}
			return E_FAIL;
		}
	};


	static station_t	ProcessStation;
};

static long nmi_startup( uint32_t minThreads = 2, size_t maxCalls = 100, bool wait = false )
{
	return nmi::ProcessStation.startup( minThreads, maxCalls, wait );
}
static long nmi_set_public_name( const wchar_t * name )
{
	return nmi::ProcessStation.set_publish_name( name );
}
static long nmi_bind_method( const char * name, nmi::imethod_t * pmo, nmi::imethod_t ** ppmo = NULL )
{
	return nmi::ProcessStation.bind_method( name, pmo, ppmo );
}
static long nmi_revoke_method( const char * name, nmi::imethod_t ** ppmo = NULL )
{
	return nmi::ProcessStation.revoke_method( name, ppmo );
}
static long nmi_async_complete( nmi::asynctx_t & async, xv::AnyValue aout )
{
	return nmi::station_t::async_complete_invoke( async, aout );
}
static long nmi_shutdown()
{
	return LacShutdown();
}
#endif